#pragma once

#include "CoreMinimal.h"
#include "Components/ShapeComponent.h"
#include "Components/AutoShape/AutoShapeConfigBase.h"
#include "AutoShapeConfigSphere.generated.h"

/**
 * 
 */
UCLASS(meta = (DisplayName = "球形碰撞"))
class BASECLASS_API UAutoShapeConfigSphere : public UAutoShapeConfigBase
{
	GENERATED_BODY()

public:
	//~ Begin UPrimitiveComponent Interface.
	virtual FPrimitiveSceneProxy* CreateSceneProxy(UAutoShapeComponent* Owner) override;
	virtual bool IsZeroExtent(const UAutoShapeComponent* Owner) const override;
	virtual struct FCollisionShape GetCollisionShape(const UAutoShapeComponent* Owner, float Inflation = 0.0f) const override;
	virtual bool AreSymmetricRotations(const UAutoShapeComponent* Owner, const FQuat& A, const FQuat& B, const FVector& Scale3D) const override;
	//~ End UPrimitiveComponent Interface.

	//~ Begin USceneComponent Interface
	virtual FBoxSphereBounds CalcBounds(const UAutoShapeComponent* Owner, const FTransform& LocalToWorld) const override;
	virtual void CalcBoundingCylinder(const UAutoShapeComponent* Owner, float& CylinderRadius, float& CylinderHalfHeight) const override;
	//~ End USceneComponent Interface

	//~ Begin UShapeComponent Interface
	virtual void UpdateBodySetup(UAutoShapeComponent* Owner) override;
	//~ End UShapeComponent Interface
public:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = Shape)
	float SphereRadius = 100.0f;

public:
	void SetSphereRadius(UAutoShapeComponent* Owner, float InSphereRadius, bool bUpdateOverlaps /*= true*/);
	FORCEINLINE float GetScaledSphereRadius(const UShapeComponent* Owner) const { return SphereRadius * GetShapeScale(Owner); }
	FORCEINLINE float GetShapeScale(const UShapeComponent* Owner) const { return Owner ? Owner->GetComponentTransform().GetMinimumAxisScale() : 1.0f; }
};

/** Represents a DrawLightRadiusComponent to the scene manager. */
class FKSphereSceneProxy final : public FPrimitiveSceneProxy
{
public:
	virtual SIZE_T GetTypeHash() const override
	{
		static size_t UniquePointer;
		return reinterpret_cast<size_t>(&UniquePointer);
	}

	/** Initialization constructor. */
	FKSphereSceneProxy(const UShapeComponent* InComponent, const UAutoShapeConfigSphere* InConfig)
		: FPrimitiveSceneProxy(InComponent)
		, bDrawOnlyIfSelected(InComponent->bDrawOnlyIfSelected)
		, SphereColor(InComponent->ShapeColor)
		, SphereRadius(InConfig->SphereRadius)
	{
		bWillEverBeLit = false;
	}

	// FPrimitiveSceneProxy interface.

	virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
	{
		QUICK_SCOPE_CYCLE_COUNTER(STAT_SphereSceneProxy_GetDynamicMeshElements);

		for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
		{
			if (VisibilityMap & (1 << ViewIndex))
			{
				const FSceneView* View = Views[ViewIndex];
				FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex);

				const FMatrix& InLocalToWorld = GetLocalToWorld();
				const FLinearColor DrawSphereColor = GetViewSelectionColor(SphereColor, *View, IsSelected(), IsHovered(), false, IsIndividuallySelected());

				// Taking into account the min and maximum drawing distance
				const float DistanceSqr = (View->ViewMatrices.GetViewOrigin() - InLocalToWorld.GetOrigin()).SizeSquared();
				if (DistanceSqr < FMath::Square(GetMinDrawDistance()) || DistanceSqr > FMath::Square(GetMaxDrawDistance()))
				{
					continue;
				}

				float AbsScaleX = InLocalToWorld.GetScaledAxis(EAxis::X).Size();
				float AbsScaleY = InLocalToWorld.GetScaledAxis(EAxis::Y).Size();
				float AbsScaleZ = InLocalToWorld.GetScaledAxis(EAxis::Z).Size();
				float MinAbsScale = FMath::Min3(AbsScaleX, AbsScaleY, AbsScaleZ);

				FVector ScaledX = InLocalToWorld.GetUnitAxis(EAxis::X) * MinAbsScale;
				FVector ScaledY = InLocalToWorld.GetUnitAxis(EAxis::Y) * MinAbsScale;
				FVector ScaledZ = InLocalToWorld.GetUnitAxis(EAxis::Z) * MinAbsScale;

				const int32 SphereSides = FMath::Clamp<int32>(SphereRadius / 4.f, 16, 64);
				DrawCircle(PDI, InLocalToWorld.GetOrigin(), ScaledX, ScaledY, DrawSphereColor, SphereRadius, SphereSides, SDPG_World);
				DrawCircle(PDI, InLocalToWorld.GetOrigin(), ScaledX, ScaledZ, DrawSphereColor, SphereRadius, SphereSides, SDPG_World);
				DrawCircle(PDI, InLocalToWorld.GetOrigin(), ScaledY, ScaledZ, DrawSphereColor, SphereRadius, SphereSides, SDPG_World);
			}
		}
	}

	virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
	{
		const bool bVisibleForSelection = !bDrawOnlyIfSelected || IsSelected();
		const bool bVisibleForShowFlags = true; // @TODO

		// Should we draw this because collision drawing is enabled, and we have collision
		const bool bShowForCollision = View->Family->EngineShowFlags.Collision && IsCollisionEnabled();

		FPrimitiveViewRelevance Result;
		Result.bDrawRelevance = (IsShown(View) && bVisibleForSelection && bVisibleForShowFlags) || bShowForCollision;
		Result.bDynamicRelevance = true;
		Result.bShadowRelevance = IsShadowCast(View);
		Result.bEditorPrimitiveRelevance = UseEditorCompositing(View);
		return Result;
	}

	virtual uint32 GetMemoryFootprint(void) const override { return (sizeof(*this) + GetAllocatedSize()); }
	uint32 GetAllocatedSize(void) const { return (FPrimitiveSceneProxy::GetAllocatedSize()); }

private:
	const uint32 bDrawOnlyIfSelected : 1;
	const FColor SphereColor;
	const float SphereRadius;
};
